package n7_wait_notify;


import lombok.extern.slf4j.Slf4j;

import static utils.Sleeper.sleep;

/*
----------------------------------------------------------------
|
|       wait 和  notify  五步 优化
|
|===============================================================
notify 按照JVM规范随机唤醒， hotspot虚拟机规定队列唤醒
notify 是随机 唤醒一个线程
notifyAll 对在所有休息室等待的 线程都叫醒
 */
@Slf4j(topic = "c.T4")
public class T4_六步优化使用_wait_notify {

    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {

//        step1();
//        step2();
//        step3();
//        step4();
//        step5();
        step6();

    }
    /*
      sleep 不会释放对象锁 占用room白白浪费时间
      小南线程必须睡足 2s 后才能醒来，就算烟提前送到，也无法立刻醒来
      --------------------------------------------
    21:07:48.308 [小南] c.T4 - 有烟没？[false]
    21:07:48.311 [小南] c.T4 - 没烟，先歇会！
    21:07:49.311 [送烟的] c.T4 - 烟到了噢！
    21:07:50.315 [小南] c.T4 - 有烟没？[true]
    21:07:50.315 [小南] c.T4 - 可以开始干活了
    21:07:50.315 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:07:50.315 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:07:50.315 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:07:50.315 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:07:50.315 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
     */
    private static void step1(){

        new Thread(() -> {
            synchronized (room) {

                log.debug("有烟没？[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟，先歇会！");
                    sleep(2);
                }

                log.debug("有烟没？[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            }, "xxxxxxxxxx 其它人").start();

        }

        sleep(1);
        new Thread(() -> {
            // 这里能不能加 synchronized (room)？
            hasCigarette = true;
            log.debug("烟到了噢！");

        }, "送烟的").start();
    }
    /*
      step1() 基础上 送烟的 加synchronized (room)
      就好比小南在里面反锁了门睡觉，烟根本没法送进门， 小南 释放锁了 ，送烟的才能执行，此时小南已结束了
      --------------------------------------------
    21:13:04.009 [小南] c.T4 - 有烟没？[false]
    21:13:04.011 [小南] c.T4 - 没烟，先歇会！
    21:13:06.019 [小南] c.T4 - 有烟没？[false]
    21:13:06.019 [送烟的] c.T4 - 烟到了噢！
    21:13:06.019 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:13:06.019 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:13:06.019 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:13:06.019 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:13:06.019 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
     */
    private static void step2(){

        new Thread(() -> {
            synchronized (room) {

                log.debug("有烟没？[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟，先歇会！");
                    sleep(2);
                }

                log.debug("有烟没？[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            }, "xxxxxxxxxx 其它人").start();

        }

        sleep(1);
        new Thread(() -> {
            // 这里能不能加 synchronized (room)？
            synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了噢！");
            }

        }, "送烟的").start();
    }

    /*
      wait()+notify() 替 sleep()
      解决了其它干活的线程阻塞的问题
      notify 是随机 唤醒一个线程, 如果有其他线程也在等待 将不一定正确唤醒
      --------------------------------------------
    21:16:57.665 [小南] c.T4 - 有烟没？[false]
    21:16:57.668 [小南] c.T4 - 没烟，先歇会！
    21:16:57.668 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:16:57.668 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:16:57.668 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:16:57.668 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:16:57.668 [xxxxxxxxxx 其它人] c.T4 - 可以开始干活了
    21:16:58.679 [送烟的] c.T4 - 烟到了噢！
    21:16:58.680 [小南] c.T4 - 有烟没？[true]
    21:16:58.680 [小南] c.T4 - 可以开始干活了
     */
    private static void step3(){

        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没？[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟，先歇会！");
                    try {
                        room.wait(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没？[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            }, "xxxxxxxxxx 其它人").start();
        }

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了噢！");
                room.notify();
            }
        }, "送烟的").start();

    }

    /*
      在 step3 基础上 创建两个线程
        notify 只能随机唤醒一个 WaitSet 中的线程，这时如有其它线程也在等，可能唤醒不了正确的线程，称之为【--虚假唤醒--】
        解决方法，改为 notifyAll （见 step5）
      --------------------------------------------
    21:25:39.217 [---小南---] c.T4 - 有烟没？[false]
    21:25:39.220 [---小南---] c.T4 - 没烟，先歇会！
    21:25:39.220 [+++小女+++] c.T4 - 外卖送到没？[false]
    21:25:39.220 [+++小女+++] c.T4 - 没外卖，先歇会！
    21:25:40.224 [送外卖的] c.T4 - 外卖到了噢！
    21:25:40.225 [---小南---] c.T4 - 有烟没？[false]
    21:25:40.225 [---小南---] c.T4 - 没干成活...
     */
    private static void step4(){

        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没1？[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟，先歇会！");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没2？[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                } else {

                    log.debug("没干成活...");
                }
            }
        }, "---小南---").start();

        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外卖送到没1？[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("没外卖，先歇会！");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖送到没2？[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        }, "+++小女+++").start();

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢！");
                room.notify();
            }
        }, "送外卖的").start();


    }

    /*
      在 step4 基础上 改为 notifyAll
       notifyAll 仅解决某个线程的唤醒问题，但使用 if + wait 判断仅有一次机会，一旦条件不成立，就没有重新判断的机会了
        解决方法，用 while + wait，当条件不成立，再次 wait (见step6)
      --------------------------------------------
    21:26:41.562 [---小南---] c.T4 - 有烟没？[false]
    21:26:41.567 [---小南---] c.T4 - 没烟，先歇会！
    21:26:41.567 [+++小女+++] c.T4 - 外卖送到没？[false]
    21:26:41.567 [+++小女+++] c.T4 - 没外卖，先歇会！
    21:26:42.575 [送外卖的] c.T4 - 外卖到了噢！
    21:26:42.576 [+++小女+++] c.T4 - 外卖送到没？[true]
    21:26:42.576 [+++小女+++] c.T4 - 可以开始干活了
    21:26:42.576 [---小南---] c.T4 - 有烟没？[false]
    21:26:42.576 [---小南---] c.T4 - 没干成活...
     */
    private static void step5(){

        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没？[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟，先歇会！");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没？[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                } else {

                    log.debug("没干成活...");
                }
            }
        }, "---小南---").start();

        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外卖送到没？[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("没外卖，先歇会！");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖送到没？[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        }, "+++小女+++").start();

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢！");
                room.notifyAll();
            }
        }, "送外卖的").start();
    }

    /*
    ------------------------
    |-- 解决虚假唤醒 问题  --|
    =======================
      在 step5 基础上 改为 while + wait ，可以进行 [-- 下一轮的wait等待 --]
     --------------------------------------------
    21:31:39.950 [---小南---] c.T4 - 有烟没？[false]
    21:31:39.954 [---小南---] c.T4 - 没烟，先歇会！
    21:31:39.954 [+++小女+++] c.T4 - 外卖送到没？[false]
    21:31:39.955 [+++小女+++] c.T4 - 没外卖，先歇会！
    21:31:40.954 [送外卖的] c.T4 - 外卖到了噢！
    21:31:40.954 [+++小女+++] c.T4 - 外卖送到没？[true]
    21:31:40.954 [+++小女+++] c.T4 - 可以开始干活了
    21:31:40.954 [---小南---] c.T4 - 没烟，先歇会！
     */
    private static void step6(){

        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没？[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("没烟，先歇会！");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                log.debug("可以开始干活了");
            }
        }, "---小南---").start();

        new Thread(() -> {
            synchronized (room) {

                log.debug("外卖送到没？[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("没外卖，先歇会！");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                log.debug("可以开始干活了");
            }
        }, "+++小女+++").start();

        sleep(3);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢！");
                room.notifyAll();
            }
        }, "送外卖的").start();
    }















}
